Repository navigation
CLI: ls, stat, cat and get subcommands for remote files (#148) - #181
bertysentry merged 3 commits into
Conversation
Closing a command early sends the terminate Signal. The service kills the process at once, but when the process is blocked writing a block larger than its output pipe, it answers the Signal only when the Signal's OperationTimeout expires (WSManFault 2150858793, measured on Windows Server 2022). Every remote file read writes 64 KB lines, so closing openStream(), openReader() or a listing's stream() early took the whole timeout, then threw from close(). The early-close Signal now asks for a 1 s hold, and the expiry of that hold is not a failure: it is a complete exchange, the connection stays in sync, and the process is gone. Early close of a remote read: 30 s and an exception before, 1 s after. Found with the CLI's "cat ... | head" (#148). Co-Authored-By: Claude Opus 5.5 <[email protected]>
Thin mappings onto the remote file access of the library:
- ls <dir>: file(dir).list()...stream(), one fixed line per entry (mode,
size, ISO-8601 UTC time with 100 ns precision, path), written as the host
walks the tree. --glob, --recursive, --depth, --files-only,
--directories-only, --modified-after, --min-size, --json.
- stat <path>: file(path).info(), one field per line, or --json.
- cat <file>: file(path).openStream() copied to stdout byte for byte;
--offset (negative: from the end), --length, --charset.
- get <file> [<local path>]: file(path).downloadTo(...), into the current
directory under the remote name by default.
Options follow the subcommand; remote paths are passed through untouched.
JsonLinesWriter now writes numbers as JSON numbers (WQL rows carry strings
only, so the wql output is unchanged).
New exit codes: 1 when ls could not read some directories (each reported on
stderr), 66 for a remote path not found, 74 for a local I/O failure (stdout
closed, or the local file of get, previously reported as a connection
failure). A closed stdout ("| head") stops the remote transfer.
cli.md documents the subcommands, their options, output formats, exit codes
and the local shell quoting of Windows paths; it also fixes four anchors
broken under Doxia 2. --help lists the new options, README gets one example.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
@codex please review again |
|
Codex Review: Something went wrong. Try again later by commenting “@codex review”. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex please review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3d02741cd1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- README: the new example is removed. README changes only when a change alters what it already shows (AGENTS.md); cli.md covers the subcommands. - A blank --glob value (e.g. an empty shell variable) is now a usage error (64), like every other file option, instead of reaching the library and exiting 70. - cli.md: exit code 74 covers file-system errors on the local file of get; a plain IOException such as a full disk keeps its message but exits 69. Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
@codex please review again |
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Closes #148. This is the last step of the remote file access family, after #146 (merged in #177), #145 (#179) and #147 (#180).
What changed
Four CLI subcommands, each a thin mapping onto a library terminal:
ls <directory>file(dir).list()...stream()stat <path>file(path).info()cat <file>file(path).openStream(), copied to stdoutget <file> [<local path>]file(path).downloadTo(...)lswrites one fixed, locale-independent line per entry, flushed as the host walks the tree:Modecolumn of Windows PowerShell (darhsl). Then the size, the last write time, and the path. The time is ISO-8601 UTC with the 100 ns precision of Windows file times, always 28 characters, so it aligns and sorts as a string.Options:
--glob,--recursive,--depth,--files-only,--directories-only,--modified-after <ISO-8601 date or date-time>,--min-size,--json.statprints the same fields, onename: valueper line, or--json.--jsonreusesJsonLinesWriter, which now writes numbers (size, attributes) as JSON numbers. WQL rows carry strings only, so thewqloutput is unchanged.catcopies the bytes to stdout: no charset round trip, no newline translation.--offset(negative: from the end) and--lengthmap to the ranged read.--charsetdecodes the file and prints it as text in the local console encoding.getis the digest-verified download. Without a local path, the file goes into the current directory under its remote name. Nothing is printed on success.Exit codes
New, documented in
cli.md:1:lslisted the tree but could not read some directories; each one is reported on stderr.66(EX_NOINPUT): remote path not found.statgets it from the emptyinfo().ls,catandgetget it from the library's "Remote path not found" message, the only thing that tells this failure apart.74(EX_IOERR): local I/O failure. Either stdout is closed or unwritable, orgethits a file-system error on its local file (aFileSystemException: access denied, a missing drive), which used to come out as a connection failure (69). A plainIOExceptionsuch as a full disk keeps its message but still exits 69.When stdout is closed early (
cat ... | head),catandlsstop the remote transfer instead of finishing it for nobody.A library bug this found: closing a remote read early
Live,
cat ... | headtook the whole timeout (121 s with-t 120000), then exited 69 with "Read timed out".The cause, measured on Windows Server 2022: closing a command early sends the terminate Signal. The service kills the process at once. But when the process is blocked writing a block larger than its output pipe, the service answers the Signal only when the Signal's OperationTimeout expires (WSManFault 2150858793). Every
RemoteFileread writes 64 KB lines, for throughput. SoopenStream(),openReader()and a listing'sstream()took the whole timeout to close early, then threw fromclose(), masking what the caller had read. A plain command writing 4,000-byte blocks closes in 18 ms; with 65,533-byte blocks, it takes 30 s.Fix, in
WsmanClient, which every caller goes through (separate commit, 9271931): the early-close Signal asks for a 1 s hold instead of the inactivity timeout. The expiry of that hold is not a failure: it is a complete exchange, the connection stays in sync, and the process is gone (checked from a second client). After the fix, an early close takes 1.0 s, andcat | headexits 74 in 1.1 s (0.98 s on anaxagore).FakeWsmanServeranswers every Signal instantly, which is why no test could catch this before.Where this departs from the issue
--lengthwithout--offsetis accepted: it reads from the start of the file, likehead -c. The issue listed it as a usage error, but nothing about it is ambiguous.getshows no progress. The library'sdownloadTohas no progress hook, and adding one means new public API. Java also cannot tell whether stderr is a terminal:System.console()looks at stdin and stdout. Sogetis silent, likecp. This is an easy follow-up if wanted.Tests
CliArgumentsTestcovers every new subcommand and option, and the usage errors: bad--depth, malformed--modified-after, unknown charset, a blank--glob, an option on the wrong subcommand,--files-onlywith--directories-only, a missing or extra path, an invalid local path, and-d/--envon a file subcommand.WinRmCliTestruns end to end through the real connect factory againstFakeWsmanServer:lsstreams: the first entry reaches stdout before the second Receive is even sent. The test also checks the long format, and that an inaccessible directory goes to stderr with exit 1.--jsonline is checked exactly.stattext and JSON. A missing path exits 66 forstat,cat,lsandget.catis byte-exact: every byte value, CRLF, a lone LF and CR, invalid UTF-8, a UTF-16 BOM and Ctrl+Z, across two blocks and two Receives.catwith a range and a charset.getinto a directory; an unwritable local destination exits 74.StreamingApiTest: an early terminate answered by the expiry of its 1 s hold is not a failure, and the connection stays usable.JsonLinesWriterTest: numbers.Verification
mvn clean verify siteon JDK 17: 316 tests pass; 0 Checkstyle, PMD, CPD and SpotBugs findings.catof a 3 MiB binary file (every byte value, then random data) redirected to a local file: identical SHA-256, in 3.5 s and 2.8 s.lsfilter, recursion and depth.--jsonwith a non-ASCII file name comes out as exact UTF-8.statof a file, a directory, and a missing path (66).catof a range, a tail, with a charset, and of a path with a space.catof a directory andlsof a file exit 70.lsandgetof a missing path exit 66.getwithout a local path, into a directory, and to a name with a space. A secondgetis skipped (about 1 s).lsover a directory that another process holds open with no sharing reports it on stderr, leaves out its content, and exits 1. That lock produces a sharing violation even for an admin session, which holds the backup privilege.cat ... | head -c 1000exits 74 in about 1 s.Docs
cli.md, the single source of truth, now has:--jsonfields,get, and the local shell quoting of Windows paths;It also fixes four
#Capitalized_Underscoreanchors that are broken under Doxia 2.The quoting notes were checked in Git Bash, cmd.exe and Windows PowerShell 5.1. That includes the trailing-backslash trap: in cmd.exe and PowerShell 5.1,
"C:\my logs\"swallows its closing quote and merges the next arguments into it. It also includes that PowerShell 5.1's>re-encodes binary output (256 bytes became 519, with a BOM). The one line on PowerShell 7.4+ keeping the bytes comes from its release notes: PowerShell 7 isn't installed here.--help: the four synopsis lines and one line per option. The details stay in the manual.README is unchanged, per AGENTS.md (the example the issue asked for was removed after the Codex review).
files.mdgets a pointer to the manual.🤖 Generated with Claude Code